/* NOTICE
 
    Jim - the Java Image Manager, copyright (c) 2005 - 2007 Grant Gardner 
 
    Jim is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Jim is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
    In addition, as a special exception, you are permitted to link this code
    with the 3rd party libraries required to compile this file and to
    distribute a linked combination including them. You must obey the
    GNU General Public License in all respects for all of the code except for
    those 3rd party libraries that are licenced under terms incompatible with the
    GNU General Public License.  
 
 
 NOTICE */

/*
 * Created on 2/08/2004
 *
 */

package au.com.lastweekend.jim.io.jpeg;

/**
 * @author grant@lastweekend.com.au
 * @version $Id: RunLengthEncodedDCTBlock.java,v 1.5 2006/02/20 12:21:10 ggardner Exp $
 */
@SuppressWarnings("PMD.AvoidUsingShortType")
public class RunLengthEncodedDCTBlock extends AbstractDCTBlock {

    private short dcCoefficient;
    private byte[] indexes;
    private short[] nonZeroValues;

    // private static final boolean DEBUG_ENABLED = LOG.isDebugEnabled();

    /**
     * @param bitBuffer
     * @throws JpegProcessingException
     */
    @Override
    public void readCoefficients(ScanComponent component, JpegBitBuffer bitBuffer) throws JpegProcessingException {

        // Decode DC co-efficient
        dcCoefficient = (short) (decodeDCCoefficient(component.getDCHuffmanTable(), component.getDCPredictor(), bitBuffer));

        // Get run-length encoded AC co-efficients

        byte[] workingIndexes = component.getWorkingIndexes();
        short[] workingValues = component.getWorkingValues();
        int numACValues = decodeACCoefficients(component.getACHuffmanTable(), workingIndexes, workingValues, bitBuffer);

        indexes = new byte[numACValues];
        nonZeroValues = new short[numACValues];
        System.arraycopy(workingIndexes, 0, indexes, 0, numACValues);
        System.arraycopy(workingValues, 0, nonZeroValues, 0, numACValues);

    }

    /**
     * See Jpeg spec F.2.2.1
     * 
     * @param x
     * @param y
     * @param bitBuffer
     * @throws JpegProcessingException
     */
    private int decodeDCCoefficient(HuffmanTable dcHuffmanTable, int dcPredictor, JpegBitBuffer bitBuffer)
            throws JpegProcessingException {

        byte bits = dcHuffmanTable.decode(bitBuffer);

        int diff = 0;

        if (bits > 0) {
            diff = bitBuffer.getBits(bits);
            diff = JpegBitOps.extend(diff, bits);
        }

        // if (isDebugEnabled()) {
        // LOG.debug("DC: pred=" + dcPredictor + " diff=" + diff + " bits=" +
        // bits);
        // }

        return dcPredictor + diff;
    }

    /*
     * private boolean isDebugEnabled() {
     * 
     * return DEBUG_ENABLED; }
     */
    /**
     * 
     * See Jpeg spec F.2.2.2, figures F.13, F.14 <br>
     * Halve the amount of memory used (depending on the efficiency of the implementation of an array) by using 2 bytes per
     * co-efficient instead of a single 4 byte int
     * 
     * @param x
     * @param y
     * @param bitBuffer
     * @return the number of codedCoefficents.
     * @throws JpegProcessingException
     */
    private int decodeACCoefficients(HuffmanTable acHuffmanTable, byte[] indexes, short[] values, JpegBitBuffer bitBuffer)
            throws JpegProcessingException {

        int numValues = 0;
        int currentIndex = 1; // start at 1, 0th value is the dc co-efficient.

        while (currentIndex < 64) {

            byte rrrrssss = acHuffmanTable.decode(bitBuffer);
            int runLength = JpegBitOps.getHigh4Bits(rrrrssss); // Number of
            // zero
            // co-efficients
            int bits = JpegBitOps.getLow4Bits(rrrrssss); // Number of bits in
            // next non zero
            // co-efficient

            if (bits == 0) {
                if (runLength == 15) {
                    currentIndex += 16;
                    // if (isDebugEnabled()) {
                    // LOG.debug("AC: rrrr=" + runLength + " ssss=" + bits + "
                    // value=0");
                    // }
                } else {
                    currentIndex = 64;
                    // if (isDebugEnabled()) {
                    // LOG.debug("AC: no more co-efficients");
                    // }
                }
            } else {
                currentIndex += runLength;
                indexes[numValues] = (byte) (currentIndex & 0xFF);
                values[numValues] = (short) (JpegBitOps.extend(bitBuffer.getBits(bits), bits));
                // if (isDebugEnabled()) {
                // LOG.debug("AC: rrrr=" + runLength + " ssss=" + bits + "
                // value=" + values[numValues]);
                // }
                numValues++;
                currentIndex++;
            }
        }

        return numValues;

    }

    /**
     * @param component
     * @param bitOutput
     * @throws JpegProcessingException
     */
    @Override
    public void writeCoefficients(ScanComponent component, JpegBitOutput bitOutput) throws JpegProcessingException {

        encodeDCCoefficient(component.getDCHuffmanTable(), component.getDCPredictor(), bitOutput);
        encodeACCoefficients(component.getACHuffmanTable(), bitOutput);

    }

    /**
     * @param bitOutput
     * @throws JpegProcessingException
     */
    private void encodeACCoefficients(HuffmanTable acHuffmanTable, JpegBitOutput bitOutput) throws JpegProcessingException {

        int currentIndex = 1;
        for (int i = 0; i < indexes.length; i++) {
            byte index = indexes[i];
            short value = nonZeroValues[i];
            int runLength = index - currentIndex;
            currentIndex = index;
            while (runLength > 15) {
                runLength = runLength - 16;
                acHuffmanTable.encode(15 << 4, bitOutput);
                if (bitOutput.isDebugEnabled()) {
                    bitOutput.debug("ACenc: ZRL for 16 consecutive zeroes");
                }
            }
            int size = JpegBitOps.getCategorySize(value);
            int rrrrssss = (((runLength << 4) + size) & 0xFF);
            if (bitOutput.isDebugEnabled()) {
                bitOutput.debug("ACenc rrrr=" + runLength + " ssss=" + size + " value=" + value);
            }

            acHuffmanTable.encode(rrrrssss, bitOutput);
            if (value < 0) {
                value--;
            }
            bitOutput.writeBits(size, value);
            currentIndex++;
        }

        if (currentIndex < 64) {
            // End of Block
            acHuffmanTable.encode(0, bitOutput);
        }

    }

    @Override
    public byte[] getIndexes() {

        return indexes;
    }

    @Override
    public short[] getNonZeroValues() {

        return nonZeroValues;
    }

    @Override
    public int getDCCoefficient() {

        return dcCoefficient;
    }

    public int[][] getCoefficients() {

        throw new UnsupportedOperationException("Only a transform should convert from zero run length coding to matrix coding");
    }

}
